package com.windaka.elements.websocket.handler;

import com.alibaba.fastjson.JSON;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageInfo;
import com.windaka.elements.type.ProjectStatusType;
import com.windaka.elements.utils.CheckParamUtils;
import com.windaka.elements.websocket.model.vo.WebSocketSendDataVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static com.github.pagehelper.page.PageMethod.startPage;


/**
 * 感知布建的 WebSocket 处理服务
 * @author song
 * @date 14:09 2022/8/22
**/
@Slf4j
@Component
@RequiredArgsConstructor
public class LayoutWebSocketHandler extends TextWebSocketHandler {

    /**
     * TOKEN: SESSION
     */
    private static final Map<String, WebSocketSession> TOKEN_SESSION_MAP = new ConcurrentHashMap<>();

    /**
     * SESSION: TOKEN
     */
    private static final Map<WebSocketSession, String> SESSION_TOKEN_MAP = new ConcurrentHashMap<>();

    /**
     * TOKEN: PROJECT
     */
    private static final Map<String, String> TOKEN_PROJECT_MAP = new ConcurrentHashMap<>();

    /**
     * 给前端发送时 一批数据的个数
     */
    private static final Integer PER_NUMBER = 50;

    /**
     * TOKEN_NAME
     */
    private static final String TOKEN_NAME = "token";

    /**
     * 线程池
     */
    private final TaskExecutor taskExecutor;

    /**
     * 建立连接后
     * @author song
     * @date 14:09 2022/8/22
    **/
    @Override
    public void afterConnectionEstablished(WebSocketSession webSocketSession) {
        // 建立连接后 要保存状态
        String token = (String) webSocketSession.getAttributes().get(TOKEN_NAME);
        // 保存 Token 和 Session 对应关系
        storeSessionAndToken(webSocketSession, token);
        log.info("[LayoutWebSocketHandler => afterConnectionEstablished 建立完毕 存储Token与Session, {}]", token);
    }

    /**
     * 收到消息
     * @author song
     * @date 14:09 2022/8/22
     **/
    @Override
    public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) {
        // 暂时不处理收到的消息
    }

    /**
     * 出现错误
     * @author song
     * @date 14:09 2022/8/22
     **/
    @Override
    public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) {
        // 关闭连接
        closeSessionAndToken(webSocketSession);
        log.error("[LayoutWebSocketHandler => handleTransportError {}, {}, {}]",
                throwable.getMessage(), throwable.getCause(), throwable.fillInStackTrace());
    }

    /**
     * 对方关闭连接后
     * @author song
     * @date 14:09 2022/8/22
     **/
    @Override
    public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) {
        // 关闭连接
        closeSessionAndToken(webSocketSession);
    }

    /**
     * 根据 Token 给用户发送消息
     * @author song
     * @date 10:46 2022/8/20
     * @param token Token
     * @param object 数据 会转换成 JSON
     **/
    public static void sendMessageByToken(String token, Object object) throws IOException {
        WebSocketSession session = TOKEN_SESSION_MAP.getOrDefault(token, null);
        sendMessageBySession(session, object);
    }

    /**
     * 根据 Session 给用户发送消息
     * @author song
     * @date 10:46 2022/8/20
     * @param session Session
     * @param object 数据 会转换成 JSON
     **/
    public static void sendMessageBySession(WebSocketSession session, Object object) throws IOException {
        if (null == session) {
            return;
        }
        // 处理成JSON
        session.sendMessage(new TextMessage(JSON.toJSONString(object)));
        log.info("[LayoutWebSocketHandler => sendMessageBySession: 发送消息: {}]", session.getId());
    }

    /**
     * 保存 Session 和 Token 的关系
     * @author song
     * @date 10:43 2022/8/20
     **/
    public void storeSessionAndToken(WebSocketSession session, String token) {
        TOKEN_SESSION_MAP.put(token, session);
        SESSION_TOKEN_MAP.put(session, token);
        log.info("[LayoutWebSocketHandler => storeSessionAndToken {}, {}]", TOKEN_SESSION_MAP, SESSION_TOKEN_MAP);
    }

    /**
     * 关闭Session
     * 并且关闭Session和清除存储
     * @author song
     * @date 10:20 2022/8/20
     **/
    public void closeSessionAndToken(WebSocketSession session) {
        try {
            // 清除状态
            // session -> token
            String token = SESSION_TOKEN_MAP.get(session);
            SESSION_TOKEN_MAP.remove(session);
            if (CheckParamUtils.isNotNullAndNotEmpty(token)) {
                // token -> session
                WebSocketSession tokenSession = TOKEN_SESSION_MAP.get(token);
                TOKEN_SESSION_MAP.remove(token);
                tokenSession.close();
            }
            session.close();
            log.info("[LayoutWebSocketHandler => closeSessionAndToken 关闭连接 {}]", token);
        } catch (IOException e) {
            log.error("[LayoutWebSocketHandler => closeSessionAndToken 关闭错误 {}, {}, {}]",
                    session.getId(), e.getMessage(), e.getCause());
        }
        log.info("[LayoutWebSocketHandler => storeSessionAndToken {}, {}]", TOKEN_SESSION_MAP, SESSION_TOKEN_MAP);
    }

    /**
     * 发送的测试样例
     * @author song
     * @date 09:49 2022/8/22
     * @param projectCode 项目编码
     * @param token 用户 token
     **/
    public void pageQuerySendBySomeThing(String projectCode, String token) {
        if (CheckParamUtils.isNullOrEmpty(projectCode) || CheckParamUtils.isNullOrEmpty(token)) {
            return;
        }
        // 删除Token相关的
        TOKEN_PROJECT_MAP.remove(token);
        // 启动线程 传输数据
        taskExecutor.execute(() -> {
            // 存储Token
            String timestamp = System.currentTimeMillis() + "";
            TOKEN_PROJECT_MAP.put(token, projectCode + timestamp);
            int page = 0;
            int limit = PER_NUMBER;
            boolean stop = false;
            // 开始传输
            try {
                sendWebSocketDataStart(token, page);
            } catch (Exception e) {
                // 停止
                stop = true;
                log.error("[LayoutWebSocketHandler => taskExecutor 线程任务 sendWebSocketDataStart 出错!: page: {}, token: {}, {}, {}]",
                        page, token, e.getMessage(), e.getCause());
            }
            // 循环 批量传输数据
            // ------- 死循环 -------
            while (!stop) {
                // 查询数据
                // 分页
                Page<?> pageHelper = startPage(page, limit);
                // 查询数据
                // mapper.select...

                // 获取分页的结果
                PageInfo<?> pageInfo = new PageInfo<>(pageHelper);
                int allPage = pageInfo.getPages();
                // 下一页
                page ++;
                // 推送
                try {
                    sendWebSocketData(token, page, allPage);
                    Thread.sleep(100);
                } catch (Exception e) {
                    // 停止
                    stop = true;
                    log.error("[LayoutWebSocketHandler => taskExecutor 线程任务 sendWebSocketDataByLayoutDevice 出错!: " +
                                    "page: {}, token: {}, {}, {}]",
                            page, token, e.getMessage(), e.getCause());
                    Thread.currentThread().interrupt();
                }

                // 如果 projectCode 切换了 则结束
                String beforeProjectCode = TOKEN_PROJECT_MAP.getOrDefault(token, "");
                // 当前的 projectCode 和 之前的是否一致
                boolean isSameProjectCode = beforeProjectCode.replace(timestamp, "").equals(projectCode);
                // 是否结束
                boolean needBreak =
                        stop
                                || page >= allPage
                                || Boolean.FALSE.equals(isSameProjectCode);
                if (needBreak) {
                    break;
                }
            }
            // --------- 死循环结束 ---------
            // 结束传输
            try {
                sendWebSocketDataEnd(token, page);
            } catch (Exception e) {
                log.error("[LayoutWebSocketHandler => taskExecutor 线程任务 sendWebSocketDataEnd 出错!: " +
                                "page: {}, token: {}, {}, {}]",
                        page, token, e.getMessage(), e.getCause());
            }
            // 结束 删除token
            TOKEN_PROJECT_MAP.remove(token);
        });
    }

    /**
     * 开始发送数据
     * @author song
     * @date 22:08 2022/8/22
    **/
    private void sendWebSocketDataStart(String token, int page) throws IOException {
        log.info("[LayoutWebSocketHandler => 开始 taskExecutor 线程任务: page: {}, token: {}]", page, token);
        WebSocketSendDataVO webSocketSendDataVO = WebSocketSendDataVO
                .builder()
                .loadName(ProjectStatusType.RUNNING.value)
                .loadProcess(0)
                .build();
        sendMessageByToken(token, webSocketSendDataVO);
    }

    /**
     * 结束发送数据
     * @author song
     * @date 22:09 2022/8/22
    **/
    private void sendWebSocketDataEnd(String token, int page) throws IOException {
        log.info("[LayoutWebSocketHandler => 结束 taskExecutor 线程任务: page: {}, token: {}]", page, token);
        WebSocketSendDataVO webSocketSendDataVO = WebSocketSendDataVO
                .builder()
                .loadName(ProjectStatusType.FINISHED.value)
                .loadProcess(100)
                .build();
        sendMessageByToken(token, webSocketSendDataVO);
    }

    /**
     * 发送数据中
     * @author song
     * @date 22:09 2022/8/22
    **/
    private void sendWebSocketData(
            String token,
            int page,
            int allPage) throws IOException {
        log.info("[LayoutWebSocketHandler => taskExecutor 线程任务: page: {}, allPage: {}, token: {}]",
                page, allPage, token);
        if (allPage <= 0) {
            return;
        }
        int percent = 100 * page / allPage;
        WebSocketSendDataVO webSocketSendDataVO = WebSocketSendDataVO
                .builder()
                .loadName(ProjectStatusType.RUNNING.value)
                .loadProcess(percent)
                .build();
        sendMessageByToken(token, webSocketSendDataVO);
    }

}
